Skip to content

fix(dev-preview): a running relay owns 8080 — a stale watch pid no longer turns it into "held by another process" - #2575

Merged
2witstudios merged 1 commit into
masterfrom
fix/preview-slot-trusts-running-relay
Sep 8, 2026
Merged

fix(dev-preview): a running relay owns 8080 — a stale watch pid no longer turns it into "held by another process"#2575
2witstudios merged 1 commit into
masterfrom
fix/preview-slot-trusts-running-relay

Conversation

@2witstudios

@2witstudios 2witstudios commented Sep 8, 2026

Copy link
Copy Markdown
Owner

What you saw

Port 8080 is already in use by something that is not the preview relay. … Port 8080 is held by another process in the sandbox (pid 11264).

What was actually true (read from the sandbox)

ss -ltnp on the env sprite: 8080 is held by our own relay (node -e … 8080 3000, pid 4698, running, started 20:45 by the re-pick); :3000 is next-server. Pid 11264 no longer exists — it was the previous relay incarnation (created 17:48 by the first pick). The realtime ports/watch snapshot still carried pid 11264 because the channel never reported the re-bind.

describeHttpPortSlot compared the snapshot's pid with the relay's, saw a mismatch, and called the relay a stranger — so the status read blocked, canOpen was false, and the planner refused the pick with http-port-busy.

Fix

A running relay bound 8080 or it would have exited (server.on('error') → process.exit(1)) and stopped being running. So a listener beside a live relay is the relay, whatever a watch pid says; only a fresh probe pid that differs may name a user process. HttpPortSlotInput gains listenerSource (default 'watch'), threaded through buildDevPreviewStatus, the planner's http-port-busy check and describeServiceState. The select path already re-probes and plans with 'probe', so a genuine stranger on 8080 is still refused there.

Realtime's fold already replaces a same-port entry on port_opened; the channel just never sent one — no realtime change.

Tests

Stale watch pid → relay; same disagreement from a probe → user-process; planner does not refuse on a watch pid but does on a probe pid; describeServiceState is live on a watch pid, blocked on a probe pid. The two existing assertions that expected refusal on any pid mismatch now say probe. Mutation-checked: removing the source check fails exactly the new assertions.

After deploy

The pane for the pinned :3000 shows Live and the frame renders (given #2572 is live too). No re-pick needed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P8A8KLYCoViP7ouH5QzbnB

Summary by CodeRabbit

  • Bug Fixes
    • Improved development preview status reporting when listener information is outdated, preventing valid previews from being incorrectly marked as blocked.
    • Development previews now continue when a stale process identifier conflicts with an active preview relay.
    • Preserved safeguards for confirmed port conflicts, which continue to be reported as blocked when an unrelated process is using the port.
    • Improved HTTP port status descriptions for more accurate ownership reporting.

…y otherwise

Production, the first re-pick: the relay restarted under a new pid (4698);
the realtime ports/watch snapshot still carried the previous relay's pid
(11264), because the channel never reported the re-bind. describeHttpPortSlot
compared the two, called the relay a stranger, and the pane read "Port 8080
is held by another process in the sandbox (pid 11264)" while the planner
refused the pick with http-port-busy — against a healthy relay.

A `running` relay bound 8080 or it would have exited (`server.on('error')`
→ `process.exit(1)`) and stopped being running; so a listener beside a live
relay IS the relay, whatever a watch pid says. Only a fresh `probe` pid that
differs may name a user process. `HttpPortSlotInput.listenerSource` carries
that, threaded through the status, the planner and describeServiceState.

Tests: the stale-watch-pid case (relay), the same disagreement from a probe
(user process), planner and state siblings; the two existing assertions
that expected refusal on a watch pid now say `probe`. Mutation-checked.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8A8KLYCoViP7ouH5QzbnB
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 91ec965e-e1c7-4b64-8213-52e41f9655cc

📥 Commits

Reviewing files that changed from the base of the PR and between c52c7f1 and 2111cc6.

📒 Files selected for processing (3)
  • packages/lib/src/services/sandbox/preview/__tests__/dev-preview-core.test.ts
  • packages/lib/src/services/sandbox/preview/dev-preview-core.ts
  • packages/lib/src/services/sandbox/preview/dev-preview-status.ts

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The preview logic now treats listener PIDs from watch snapshots as potentially stale when a relay is live. Authoritative probe PIDs still distinguish relay ownership from user processes. Service planning, status reporting, and tests now propagate and validate the listener source.

Changes

Preview slot classification

Layer / File(s) Summary
Listener source slot classification
packages/lib/src/services/sandbox/preview/dev-preview-core.ts, packages/lib/src/services/sandbox/preview/dev-preview-status.ts
HttpPortSlotInput accepts listenerSource. Watch snapshots attribute a listener beside a live relay to the relay. Probe snapshots retain PID comparison. Service planning and status reporting forward the source.
Classification regression coverage
packages/lib/src/services/sandbox/preview/__tests__/dev-preview-core.test.ts
Tests cover watch and probe outcomes for slot availability, service planning, and service state reporting.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 2111c

Preview port ownership now tolerates stale watch PIDs for a live relay while fresh probe conflicts remain blocked, avoiding false blocked states without weakening confirmed port-conflict handling.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: a running relay retains ownership of port 8080 when the watch PID is stale.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 4 functions across 3 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/preview-slot-trusts-running-relay

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@2witstudios
2witstudios merged commit 1edb0ef into master Sep 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant